//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include <init.h>

#ifdef _neptune

#include <Zenr_api.h>
#include <nu_api.h>
#include "..\..\..\inc\util\hal\arm\Interlock.h"
struct TimerExceptionStack;
extern "C" TimerExceptionStack g_timerStack;
extern "C" void CDECL TimerExceptionHandler();
extern "C" InterruptContext g_timerContext;;
extern "C" bool_t g_bPreemption;
extern Bool32 g_allDevicesDown;
extern Bool32 g_sleeped;
static Int32 s_lastRtcTime = 0;
static Int32 s_schedule = 0;
Int32 g_Debug = 0;
Int32 StopScheduleTimer() {
    return InterlockedIncrement(&s_schedule);
}

void StartScheduleTimer(UInt32 uFlag) {
    Int32 temp;
    temp = InterlockedDecrement(&s_schedule);
    assert((uFlag - temp) == 1);
}

#define SaveFlagsAndCli StopScheduleTimer
#define RestoreIF StartScheduleTimer

#endif //endif _neptune

uint_t g_uElapsedTicks  = 0;
uint_t g_uLostTicks     = 0;

void CDECL HandleTimers();

EXTERN uint_t DzGetElapsedTicks()
{
    return g_uElapsedTicks;
}

EXTERN uint_t DzGetIdleTicks()
{
    assert(0 == GetThread0()->m_uUserProcessorTicks);
    return GetThread0()->m_uPrivilegedProcessorTicks;
}

SystemTime g_systemTime;

//
// RTC
//
INLINE uint32_t RTCTimeToSeconds(const RTCTime *pRTCTime)
{
    assert(pRTCTime);

    int nMonth  = pRTCTime->uMonth;
    int nYear   = pRTCTime->uYear;
    int nDate   = pRTCTime->uDate;
    nMonth -= 2;            // 1..12 -> 11,12,1..10
    if (0 >= nMonth) {
        nMonth  += 12;      // Puts Feb last since it has leap day
        nYear   -= 1;
    }

    uint32_t uSeconds = (uint_t)(nYear / 4 - nYear / 100 + nYear / 400
            + 367 * nMonth / 12 + nDate) + nYear * 365 - 719499;

    uSeconds = uSeconds * 24 + pRTCTime->uHours;    // now have hours
    uSeconds = uSeconds * 60 + pRTCTime->uMinutes;  // now have minutes
    uSeconds = uSeconds * 60 + pRTCTime->uSeconds;  // finally seconds

    return uSeconds;
}

INLINE void SecondsToRTCTime(uint32_t uSeconds, RTCTime *pRTCTime)
{
    assert(pRTCTime);

#define SECONDS_PER_DAY     (3600 * 24)

    uint32_t uHoursMinutesSeconds = uSeconds % SECONDS_PER_DAY;
    pRTCTime->uHours    = uHoursMinutesSeconds / 3600;
    pRTCTime->uMinutes  = (uHoursMinutesSeconds % 3600) / 60;
    pRTCTime->uSeconds  = uHoursMinutesSeconds % 60;

#define IS_LEAPYEAR(y)      \
    (((0 == (y) % 4) && (0 != (y) % 100)) || (0 == (y) % 400))
#define DAYS_PER_YEAR(y)    (IS_LEAPYEAR(y)? 366u : 365u)

    uint32_t uDays = uSeconds / SECONDS_PER_DAY;
    uint32_t uYear;
    for (uYear = 1970; uDays >= DAYS_PER_YEAR(uYear); uYear++) {
        uDays -= DAYS_PER_YEAR(uYear);
    }
    pRTCTime->uYear = uYear;

    uint32_t uDaysPerMonth[12] = {
        31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
    };

#define DAYS_PER_MONTH(m)   (uDaysPerMonth[(m) - 1])
    if (IS_LEAPYEAR(uYear)) DAYS_PER_MONTH(2) = 29;

    uint32_t uMonth;
    for (uMonth = 1; uDays >= DAYS_PER_MONTH(uMonth); uMonth++) {
        uDays -= DAYS_PER_MONTH(uMonth);
    }
    pRTCTime->uMonth = uMonth;

    pRTCTime->uDate = uDays + 1;
}

EXTERN void TriggerSystemEvent(SystemEvent sysEvent);

EXTERN void DzSyncSystemTime()
{
    bool_t bTimeChanged = FALSE;
    RTCTime rtcTime;

    uint32_t uFlags = SaveFlagsAndCli();
    BspGetRTCTime(&rtcTime);
    Int32 uSeconds = RTCTimeToSeconds(&rtcTime);
    if (uSeconds != g_systemTime.seconds) {
        bTimeChanged = TRUE;
        g_systemTime.seconds = uSeconds;
        g_systemTime.microseconds = 0;
    }
    RestoreIF(uFlags);

    if (bTimeChanged) TriggerSystemEvent(SystemEvent_TimeChanged);
}

EXTERN void DzGetSystemTime(SystemTime *pSystemTime)
{
    assert(pSystemTime);

    uint32_t uFlags = SaveFlagsAndCli();
    // TODO(mips): It's only for test now.
#ifdef _mips
    pSystemTime->seconds = 0x01234567;
    pSystemTime->microseconds = 0xfedcba98;
#elif _neptune
    RTCTime rtcTime;
    if (g_systemTime.seconds - s_lastRtcTime >= 10) {
        BspGetRTCTime(&rtcTime);
        pSystemTime->seconds = RTCTimeToSeconds(&rtcTime);
        g_systemTime.seconds = pSystemTime->seconds;
        s_lastRtcTime = pSystemTime->seconds;
        g_systemTime.microseconds = pSystemTime->microseconds = 0;
    }
    else {
        *pSystemTime = g_systemTime;
    }
#else
    *pSystemTime = g_systemTime;
#endif
    RestoreIF(uFlags);
}

EXTERN void DzSetSystemTime(SystemTime systemTime)
{
    RTCTime rtcTime;
    SecondsToRTCTime(systemTime.seconds, &rtcTime);

    uint32_t uFlags = SaveFlagsAndCli();
    BspSetRTCTime(&rtcTime);
    g_systemTime = systemTime;
    RestoreIF(uFlags);

    TriggerSystemEvent(SystemEvent_TimeChanged);
}

INLINE void UpdateSystemTimePerTick()
{
    uint32_t uFlags = SaveFlagsAndCli();
    g_systemTime.microseconds += 1000000 / c_uTickRate;
    if (1000000 <= g_systemTime.microseconds) {
        g_systemTime.microseconds -= 1000000;
        g_systemTime.seconds++;
    }
    RestoreIF(uFlags);
}

uint_t UpdateSystemTime()
{
    uint_t uLostTicks = InterlockedExchange((Int32 *)&g_uLostTicks, 0);
    for (uint_t i = uLostTicks; i > 0; i--) {
        UpdateSystemTimePerTick();
    }

    return uLostTicks;
}

DECL_ASMLINKAGE void CDECL UpdateTime(InterruptContext *pContext)
{
    uint_t uLostTicks = UpdateSystemTime();

//    kprintf("System time: %d.%d\n",
//            g_systemTime.seconds, g_systemTime.microseconds);

    UpdateCurrentThreadTime(uLostTicks, IS_IN_USER_MODE(pContext));

    bool_t bOriginalPreemption = DzDisablePreemption();
    HandleTimers();
    RestorePreemptionByIsr(bOriginalPreemption);
}

//
// PIT
//
#ifndef _neptune
STATIC void CDECL PITIsr(irq_t irq, void *pvDevice,
    InterruptContext *pContext)
{
    TIMEPEG_TIMER_INTERRUPT_STOP

    BspReloadPIT();

    g_uElapsedTicks++;
    g_uLostTicks++;
}
#endif

EXTERN DECL_CODEINIT bool_t CDECL InitTime()
{
#ifndef _neptune
    RTCTime rtcTime;
    BspGetRTCTime(&rtcTime);
    g_systemTime.seconds = RTCTimeToSeconds(&rtcTime);
    g_systemTime.microseconds = 0;

    DzRegisterIsr(bsp.irqOfPIT, bsp.iplOfPIT, &PITIsr, NULL);

    kprintf("RTC Time: %d/%02d/%02d    %02d:%02d:%02d\n",
            rtcTime.uYear, rtcTime.uMonth, rtcTime.uDate,
            rtcTime.uHours, rtcTime.uMinutes, rtcTime.uSeconds);

    InitDelayParameter();
#else
    RTCTime rtcTime;
    BspGetRTCTime(&rtcTime);

    g_systemTime.seconds = RTCTimeToSeconds(&rtcTime);
    g_systemTime.microseconds = 0;

    kprintf("RTC Time: %d/%02d/%02d    %02d:%02d:%02d\n",
            rtcTime.uYear, rtcTime.uMonth, rtcTime.uDate,
            rtcTime.uHours, rtcTime.uMinutes, rtcTime.uSeconds);

    nu_timer((int)TimerExceptionHandler, (void *)&g_timerStack, (int *)&g_timerContext, (int *) &g_bPreemption, &g_sleeped, &g_allDevicesDown, &s_schedule, &g_Debug);

    InitDelayParameter();

    kprintf("Init Timer OK!\n");
#endif

    return TRUE;
}

//
//  TimerList
//
class TimerList : public DLinkNode
{
public:
    TimerList()     { DLinkNode::Initialize(); }
};

#define TIMERLIST_ORDER     5   // TODO: available size ?

const uint_t        c_uNumberOfTimerLists = 1 << TIMERLIST_ORDER;
STATIC TimerList    s_timerLists[c_uNumberOfTimerLists];
STATIC uint_t       s_uNumberOfTimers = 0;

//
//  Timer
//
void DzTimer::Start()
{
    assert(0 < m_uDueTicks);

    uint32_t uFlags = SaveFlagsAndCli();
    _Start();
    RestoreIF(uFlags);
}

void DzTimer::_Start()
{

    assert(0 < m_uDueTicks);
//  uint32_t uFlags = SaveFlagsAndCli();

    register uint_t uDueTick = g_uElapsedTicks + m_uDueTicks;
    m_uDueTicks = (uDueTick < m_uDueTicks)? UINT_MAX : uDueTick;

    register TimerList *pTimerList =
        &s_timerLists[m_uDueTicks & (c_uNumberOfTimerLists - 1)];

    register DzTimer *pNextTimer;

#if !defined(_RELEASE) || defined(_PRERELEASE)
    ForEachDLinkNode(DzTimer *, pNextTimer, pTimerList) {
//      if (pNextTimer == this) {
//            pNextTimer->Detach(&s_uNumberOfTimers);
//        }
    assert(pNextTimer != this);
//      continue;
    }
#endif

    ForEachDLinkNode(DzTimer *, pNextTimer, pTimerList) {
        if (pNextTimer->m_uDueTicks >= m_uDueTicks) {
            pNextTimer->InsertPrev(this, &s_uNumberOfTimers);
            m_bCanceled = FALSE;
//          RestoreIF(uFlags);
            return;
        }
    }

    pTimerList->InsertLast(this, &s_uNumberOfTimers);
    m_bCanceled = FALSE;
//  RestoreIF(uFlags);
}

void DzTimer::Cancel()
{
    uint32_t uFlags = SaveFlagsAndCli();
    if (m_bCanceled) {
        RestoreIF(uFlags);
        return;
    }

    if (!Due()) {
        Detach(&s_uNumberOfTimers);
        m_bCanceled = TRUE;
    }
    assert(m_bCanceled);
    RestoreIF(uFlags);
}

void CDECL HandleTimers()
{
    STATIC uint_t s_uPrevElapsedTicks = 0;

    uint32_t uFlags = SaveFlagsAndCli();

    while (s_uPrevElapsedTicks < g_uElapsedTicks) {
        if (0 == s_uNumberOfTimers) {
            s_uPrevElapsedTicks = g_uElapsedTicks;
            break;
        }

        s_uPrevElapsedTicks++;

        register TimerList *pTimerList =
            &s_timerLists[s_uPrevElapsedTicks & (c_uNumberOfTimerLists - 1)];

        while (!pTimerList->IsEmpty()) {
            register DzTimer *pTimer = (DzTimer *)pTimerList->First();
            if (s_uPrevElapsedTicks < pTimer->m_uDueTicks) {
                break;
            }

            pTimer->Detach(&s_uNumberOfTimers);
            pTimer->m_uDueTicks = 0;
            pTimer->m_bCanceled = TRUE;
            RestoreIF(uFlags);

            (*pTimer->m_routine)(pTimer->m_pvParameter);

            uFlags = SaveFlagsAndCli();
        }
    }

    assert(s_uPrevElapsedTicks == g_uElapsedTicks);

    RestoreIF(uFlags);
}
